package com.chenu.gamscriptman.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.chenu.gamscriptman.compoent.ChenuMD5PasswordEncoder;
import com.chenu.gamscriptman.compoent.Global;
import com.chenu.gamscriptman.dao.UInfoDao;
import com.chenu.gamscriptman.dao.UserDao;
import com.chenu.gamscriptman.domain.noindb.ListMsgParameter;
import com.chenu.gamscriptman.domain.noindb.Message;
import com.chenu.gamscriptman.domain.UInfo;
import com.chenu.gamscriptman.domain.User;
import com.chenu.gamscriptman.domain.noindb.PageBean;
import com.chenu.gamscriptman.service.NotifyService;
import com.chenu.gamscriptman.service.UserService;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author flyan
 * date 2019-12-24
 * @function 用户服务实现类，为客户端的请求提供用户服务
 * 但使用消息通信之后，它更像个用户任务。
 */
@CacheConfig(cacheNames = "user")   /* 本类使用缓存名称：user */
@Service("userService")
public class UserServiceImpl implements UserService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /* 请求用户服务的服务路由键 */
    public static final String SERVICE_ROUTING_KEY = "user";

    /* 消息服务器的用户消息队列 */
    public static final String QUEUE = "gamscriptman.userservice.queue";

    /* 数据库数据是否被更新了 */
    public static boolean DbIsUpdate = true;

    /* redis模板，用于缓存和处理结果的暂存 */
    @Autowired
    private RedisTemplate jsonRedisTemplate;

    /* gson，用于处理json数据 */
    @Autowired
    private Gson gson;

    @Autowired
    private ChenuMD5PasswordEncoder chenuMD5PasswordEncoder;

    /* 事务管理器 */
    @Autowired
    DataSourceTransactionManager transactionManager;

    /* dao */
    @Autowired
    private UserDao userDao;
    @Autowired
    private UInfoDao uInfoDao;

    /* service */
    @Autowired
    private NotifyService notifyService;

    /* 任务能提供的所有功能索引号 */
    public static final int USER_LIST = 0;                  /* 得到所有用户信息 */
    public static final int USER_ADD_OR_EDIT_VIEW = 1;      /* 前往用户添加/编辑页面 */
    public static final int USER_ADD_OR_UPDATE = 2;         /* 用户添加或更新 */
    public static final int USER_REMOVE = 3;                /* 用户删除 */
    public static final int USER_COUNT = 4;                 /* 得到用户数量@TODO */

    /**
     * 处理客户端发来的消息，提供服务，所有的服务都需要在这里注册，并规范化
     * 服务完成后，返回一个包含处理结果的消息，这个消息将回发到最初需要服务的例程
     * @param message 客户端发来的消息
     * @return 包含处理结果的消息
     */
    @Override
    @RabbitListener(queues = QUEUE)
    public Message service(
            Message message
    ) {
        Message rs = null;  /* 就是一个指针 */

        /* 看看用户想做什么？ */
        switch (message.getType()){
            case USER_LIST:                 /* 列出所有用户 */
                logger.debug("用户任务：USER_LIST...");
                rs = do_list(message);
                break;

            case USER_ADD_OR_EDIT_VIEW:     /* 需要前往用户添加或编辑页面 */
                logger.debug("用户任务：USER_ADD_OR_EDIT_VIEW...");
                rs = do_add_or_edit_view(message);
                break;

            case USER_ADD_OR_UPDATE:        /* 添加或更新一个用户 */
                logger.debug("用户任务：USER_ADD_OR_UPDATE...");
                rs = do_add_or_update(message);
                break;

            case USER_REMOVE:               /* 删除一个用户 */
                logger.debug("用户任务：USER_REMOVE...");
                rs = do_remove(message);
                break;

            case USER_COUNT:               /* 得到用户数量 */
                logger.debug("用户任务：USER_COUNT...");
                rs = do_count(message);
                break;

            default:
                logger.warn("用户任务：你正在请求一个不存在的功能服务...");
                rs = MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.USER_TASK,
                        Message.HANDLER_FAILURE,
                        "用户任务：你请求了一个不存在的功能服务......"
                );
                break;
        }

        return rs;
    }

    private Message do_list(Message message) {
        PageBean<User> data = null;
        String rtData = null;

        /* 解析消息中的数据 */
        ListMsgParameter<User> parameter = gson.fromJson(message.getData(), new TypeToken<ListMsgParameter<User>>() {}.getType());
        if(parameter != null && parameter.getPageBean() != null) {    /* 不为null才需要 */
            parameter.setPageBean(new PageBean<User>(
                    parameter.getPageBean().getCurrent(),
                    parameter.getPageBean().getSize(),
                    true,
                    parameter.getPageBean().getDynamicHopeSize()
            ));
        }


        /* 检查查询参数 */
        boolean paging = false;     /* 是否需要分页？ */
        if(parameter == null) { /* 没有任何限制 */
            parameter = new ListMsgParameter<User>()
                    .setPageBean(null)
                    .setQueryBean(null)
                    .setEx_int(-1);
        } else {
            parameter = new ListMsgParameter<User>()
                    .setPageBean(parameter.getPageBean())
                    .setQueryBean(null)
                    .setEx_int(parameter.getEx_int());
            paging = true;
        }

        /* 检查是否可以使用更快速的缓存 */
        Integer onlyRole = (Integer) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_ONLYROLE);
        PageBean<User> pageBean = (PageBean<User>)jsonRedisTemplate.opsForValue().get(Global.REDIS_USER_PAGES);
        if(new ListMsgParameter<User>()
                .setEx_int(onlyRole)
                .setQueryBean(null)
                .setPageBean(pageBean)
                .equals(parameter) && !DbIsUpdate){
            logger.debug("用户列表 --> 使用缓存");
            /* 查询条件一样，而且数据库近期没更新，那么不需要去数据库拿了，缓存中有 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USER_TASK,
                    Message.HANDLER_SUCCESS,
                    "/users"
            );
        }
        logger.debug("用户列表 --> 数据库查询");

        /* 查询，这一步只有上面的缓存用不到才会执行到 */
        data = findAll(message.getSource(), parameter.getEx_int(), parameter.getPageBean());
        if(paging) data.calcDynamicBar();  /* 计算动态显示条 */

        if(data.getRecords() == null){       /* 查询失败了 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USER_TASK,
                    Message.HANDLER_FAILURE,
                    "用户任务：USER_LIST失败......"
            );
        }

        /* 成功处理 */
        if(message.isNeedView()){
            /* 如果需要视图，将结果和参数放入缓存，并设置返回的页面 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_USER_PAGES, data);
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_ONLYROLE, parameter.getEx_int());
            rtData = "/users";
        } else {
            /* 不需要视图，将结果转化为json串放到回复消息中 */
            rtData = gson.toJson(data);
            logger.warn("--> " + rtData);
        }
        /* 返回结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USER_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );

    }

    private Message do_add_or_edit_view(Message message) {
        User user = null;
        Long id = null;
        String rtData;

        try{
            id = Long.valueOf(message.getData());
        } catch (Exception e){
            logger.info("用户前往的是添加页面...");
        }

        if(id != null){
            /* 如果有用户id，则说明是前往编辑页面，查询该用户信息 */
            user = findById(id);
            user.setUinfo(uInfoDao.selectById(user.getUinfoFk()));
        }

        /* 需要视图？ */
        if(message.isNeedView()){
            /* 放入要编辑的用户 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_EDIT_USER, user);
            /* 设置页面 */
            rtData = "/user";
        } else {
            /* 不需要视图，将编辑的用户转为json串 */
            rtData = gson.toJson(user);
        }

        /* 这个调用必定成功 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USER_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );
    }

    private Message do_add_or_update(Message message) {
        PageBean<User> data = null;
        int rows = 0;
        boolean isInsert = false;

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        /* 解析消息中的用户数据 */
        try {
            User user = gson.fromJson(message.getData(), User.class);
            if(user.getUid() == null) isInsert = true;
            logger.info(isInsert ? "do_add --> " + user : "do_update --> " + user);
            rows = saveOrUpdate(user);
            if(rows > 0){   /* 成功 */
                notifyService.notify(message.getSource(),
                        isInsert ? "添加用户成功！" : "更新用户（" + user.getUid() + "）成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<User> pageBean = (PageBean<User>) jsonRedisTemplate.opsForValue().get(Global.REDIS_USER_PAGES);
                Integer onlyRole = (Integer) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_ONLYROLE);
                ListMsgParameter<User> parameter = new ListMsgParameter();
                parameter.setPageBean(pageBean).setEx_int(onlyRole);
                message.setData(gson.toJson(parameter));
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {
                MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.USER_TASK,
                        Message.HANDLER_FAILURE,
                        "用户任务：添加或更新的用户失败，请重试...");
            }
            /* 提交事务 */
            transactionManager.commit(transStatus);
        } catch (Exception e){  /* 处理失败 */
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            logger.debug(" --> " + message.getData());
            MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USER_TASK,
                    Message.HANDLER_FAILURE,
                    "用户任务：添加或更新的用户数据不正确...");
        }

        return message;
    }

    private Message do_remove(Message message) {
        PageBean<User> data = null;
        int rows = 0;

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);
        try {
            Long id = Long.valueOf(message.getData());
            rows = remove(id);
            if(rows > 0){   /* 删除成功 */
                /* 给用户一条通知提示成功提示信息 */
                notifyService.notify(message.getSource(), "用户（" + id + "）删除成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<User> pageBean = (PageBean<User>) jsonRedisTemplate.opsForValue().get(Global.REDIS_USER_PAGES);
                Integer onlyRole = (Integer) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_ONLYROLE);
                ListMsgParameter<User> parameter = new ListMsgParameter();
                parameter.setPageBean(pageBean).setEx_int(onlyRole);
                message.setData(gson.toJson(parameter));
                /* 提交事务 */
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {
                transactionManager.rollback(transStatus);
                return MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.USER_TASK,
                        Message.HANDLER_FAILURE,
                        "用户任务：删除失败，这个用户可能不存在或已经被移除，请重试..."
                );
            }
        } catch (Exception e){
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USER_TASK,
                    Message.HANDLER_FAILURE,
                    "用户任务：删除失败，你未指明任何用户号"
            );
        }
    }

    private Message do_count(Message message) {
        return null;
    }

    /**
     * 查询所有用户，可以设置只看什么角色的用户
     * @param source 请求服务的用户源
     * @param onlyRole 只查看某角色用户？
     * @param page 分页数据，null不分页
     * @return 查询到的所有用户列表
     */
    public PageBean<User> findAll(Long source, Integer onlyRole, PageBean<User> page) {
        DbIsUpdate = false;
        List<User> list = new ArrayList<>();
        Integer userRole = userDao.selectRoleById(source);

        /* 如果是消费用户，谁也不能看 */
        if(source != 0 && User.ADMIN == userRole) return page;


        if(source == 0){
            /* 系统固有的ROOT用户 */
            if(User.ROOT == onlyRole){              /* 只查看ROOT用户 */
                logger.warn("--> User.ROOT == onlyRole");

                /* 查询所有ROOT角色用户 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            eq("role", User.ROOT));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            eq("role", User.ROOT));
                }
            } else if(User.ADMIN == onlyRole){      /* 只查看管理员 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            eq("role", User.ADMIN));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            eq("role", User.ADMIN));
                }
            } else if(User.CONSUME == onlyRole){    /* 只查看消费用户 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            eq("role", User.CONSUME));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            eq("role", User.CONSUME));
                }

            } else {                                /* 查看所有 */
                if(page == null){
                    list = userDao.selectList(null);
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, null);
                }
            }

        } else if(User.ROOT == userRole){
            /* ROOT角色用户 */
            if(User.ADMIN == onlyRole){      /* 只查看管理员 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            eq("role", User.ADMIN));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            eq("role", User.ADMIN));
                }
            } else if(User.CONSUME == onlyRole){    /* 只查看消费用户 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            eq("role", User.CONSUME));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            eq("role", User.CONSUME));
                }

            } else {                                /* 查看所有，但不包括ROOT角色用户 */
                if(page == null){
                    list = userDao.selectList(new QueryWrapper<User>().
                            ne("role", User.ROOT));
                    page.setRecords(list);
                } else {
                    page = userDao.selectPage(page, new QueryWrapper<User>().
                            ne("role", User.ROOT));
                }
            }
        } else {
            /* 普通管理用户，只能查看消费用户 */
            if(page == null){
                list = userDao.selectList(new QueryWrapper<User>().
                        eq("role", User.CONSUME));
                page.setRecords(list);
            } else {
                page = userDao.selectPage(page, new QueryWrapper<User>().
                        eq("role", User.CONSUME));
            }
        }



        /* 查询出用户的个人资料 */
        for (User u : page.getRecords()) {
            UInfo uInfo = uInfoDao.selectById(u.getUinfoFk());
            u.setUinfo(uInfo);
        }

        return page;
    }

    private Message do_no_call(Message message) {
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USER_TASK,
                Message.HANDLER_FAILURE,
                "用户任务：你请求了一个不存在的功能服务......"
        );
    }


    /**
     * 用户添加或更新
     */
    @Override
    public int saveOrUpdate(User user){
        DbIsUpdate = true;
        int rows = 0;
        // 根据是否有id决定是添加还是更新
        if(user.getUid() == null){
            rows = save(user);
        } else {
            rows = update(user);
        }
        return rows;
    }

    /**
     * 删除一个用户
     * 支持缓存，当删除一个用户时，也从缓存中删除
     *
     * @param uid 用户的id
     * @return 影响数据库的行数
     */
    @Override
    @CacheEvict(key = "#uid")
    public int remove(Long uid) {
        DbIsUpdate = true;
        int rows = 0;
        User user = userDao.selectById(uid);
        Long uinfoFk = null;
        if(user != null){
            uinfoFk = user.getUinfoFk();
            rows = userDao.deleteById(uid);
        }
        if(uinfoFk != null) /* 如果该用户有资料细腻，别忘了一起删除 */
            uInfoDao.deleteById(uinfoFk);
        return rows;
    }

    /**
     * 保存一个新用户
     * @param record
     * @return
     */
    @Override
    public int save(User record) {
        DbIsUpdate = true;
        logger.debug("=== 添加用户信息 ===");
        /* 先保存用户资料 */
        uInfoDao.insert(record.getUinfo());
        record.setUinfoFk(uInfoDao.selectNewId());
        /* 给密码加密 */
        record.setPassword(chenuMD5PasswordEncoder.encode(record.getPassword()));
        logger.warn("save --> " + record);
        return userDao.insert(record);
    }

    /**
     * 查询一名用户
     * 支持缓存，当第一次查询到一个用户，我们将其放入redis缓存
     *
     * @param uid 用户的唯一id
     * @return 查询到的用户，没查到自然为空
     */
    @Override
    @Cacheable(
            key = "#uid",                               /* 缓存key使用用户的id */
            cacheManager = "jsonRedisCacheManager",     /* 使用json缓存管理器 */
            unless = "#result == null"                  /* 当没查到时，不缓存 */
    )
    public User findById(Long uid) {
        return userDao.selectById(uid);
    }

    /**
     * 更新一个用户信息
     * 支持缓存，当用户信息被更新时候，同时更新缓存数据
     *
     * @param record 更新的用户
     * @return 影响数据库的行数
     */
    @Override
    @CachePut(key = "#record.uid")
    public int update(User record) {
        int rows = 0;
        DbIsUpdate = true;
        User old = userDao.selectById(record.getUid());

        /* 先更新用户 */
        if(!StringUtils.isEmpty(record.getPassword())){
            /* 如果密码被更改了，记得给它加密 */
            record.setPassword(chenuMD5PasswordEncoder.encode(record.getPassword()));
        } else {
            /* 密码没被更改，置null就代表不更新 */
            record.setPassword(null);
        }
        logger.debug("=== 更新用户信息 ===>" + record);
        rows = userDao.updateById(record);
        /* 更新用户资料 */
        UInfo uInfo = record.getUinfo();
        uInfo.setUiid(old.getUinfoFk());
        uInfoDao.updateById(uInfo);
        return rows;
    }


    /**
     * 登录，通过用户名和密码得到用户信息
     * 支持缓存，当第一次查询到的时候，放入redis缓存
     *
     * @param username 用户名
     * @param password 密码明文
     * @return 查询到的用户，没查到为空
     */
    @Override
    public User login(String username, String password) {
        return userDao.selectOne(new QueryWrapper<User>().
                eq("username", username).
                eq("password", chenuMD5PasswordEncoder.encode(password))
        );
    }

    /* 用户是否存在？如果存在，返回其用户名，否则返回null */
    @Override
    public String isExists(Long uid) {
        return userDao.selectUsernameById(uid);
    }

    /**
     * 通过用户名加载用户信息
     * 这个方法用于实现登录，它继承了 UserDetailsService 接口需要实现，作用是为Spring Security提供支持
     * @param username 用户名
     * @return 用户详情
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.selectOne(new QueryWrapper<User>().
                eq("username", username));
        if(user == null) throw new UsernameNotFoundException("用户名和密码不匹配！");   /* 不存在 */
        user.setUinfo(uInfoDao.selectById(user.getUinfoFk()));

        /* 封装用户信息并返回，三个参数分别是：用户名，密码，用户角色
         * 由于用户名和密码从数据库获取了，只需要配置用户角色即可
         */
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        if(user.getRole() == User.ROOT)     /* 超级用户 */
            authorities.add(new SimpleGrantedAuthority("ROLE_ROOT"));
        if(user.getRole() == User.ADMIN)    /* 管理员 */
            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        if(user.getRole() == User.CONSUME)  /* 消费用户 */
            authorities.add(new SimpleGrantedAuthority("ROLE_CONSUME"));
        /* 该账号没有任何有用角色，那么我们禁用这个账户 */
        if(authorities.size() == 1)
            user.setEnabled(false);

        user.setAuthorities(authorities);
        logger.info("查询到的用户 --> " + user);
        return user;
    }

}
